Justice Arcana: Minimum Spanning Trees & Circular Routing
Tags: #◇CSALGCM #◇Problem Sets #◇HackerRank #◇Algorithms #◇Greedy Algorithms
◇Overview
The Justice Arcana embodies balance and cycle-free connectivity: Minimum Spanning Trees (Kruskal's Algorithm with Disjoint Set Union) and Circular Greedy Tour Routing (The Gas Station Problem).
◇Problem 1: Minimum Spanning Tree (Kruskal's Algorithm)
Problem Description
Given a connected, undirected graph with edge weight function . A spanning tree is an acyclic subgraph connecting all vertices . Find a spanning tree that minimizes the total weight:
Constraints:
- Edge weights can be positive or negative.
Cut Property & Greedy Choice Proof
Cut Property: Let be any partition (cut) of vertices . If edge is a light edge (strictly minimum weight) crossing the cut (where ), then belongs to some Minimum Spanning Tree of .
Kruskal's Strategy:
- Sort all edges in non-decreasing order of weight: .
- Iterate through sorted edges. For each edge :
- Check if and belong to different connected components using Disjoint Set Union (DSU).
- If in different components, add to the spanning forest and unify their components.
- If in the same component, reject to prevent forming a cycle.
- Terminate when edges have been accepted.
Stepped Trace
Consider graph with and edges:
.
| Edge | Weight | Component | Component | Action | Spanning Tree Edge Count |
|---|---|---|---|---|---|
| 1 | Accept (Union ) | 1 | |||
| 2 | Accept (Union ) | 2 | |||
| 3 | Reject (Forms Cycle ) | 2 | |||
| 3 | Accept (Union ) | 3 | |||
| 4 | Accept (Union ) | 4 () |
Algorithm terminates. Total MST Weight: .
◇Problem 2: Circular Routing (The Gas Station Problem)
Problem Description
There are gas stations arranged in a clockwise circular route. Station offers gas[i] fuel, and the cost to travel from station to station is cost[i]. Starting with an empty tank, find the unique starting station index from which a car can complete one full circular tour. If no such start exists, return .
Constraints:
Greedy Reset Paradigm
- Global Feasibility: If , total energy is strictly insufficient; return .
- Greedy Start Selection:
- Maintain
tank = 0and candidatestart = 0. - Iterate from to , accumulating net fuel: .
- If
tank + \Delta_i < 0, a car starting at any station would run out of fuel before or at station . - Therefore, greedily reset
start = i + 1andtank = 0.
- Maintain
Stepped Calculation Trace
Let gas = [1, 2, 3, 4, 5] and cost = [3, 4, 5, 1, 2].
- Total gas = , Total cost = (Feasible!).
- : .
tank= Reset:start= 1,tank= 0. - : .
tank= Reset:start= 2,tank= 0. - : .
tank= Reset:start= 3,tank= 0. - : .
tank= . - : .
tank= .
Unique Valid Starting Station: Index 3 (0-indexed).
◇Complete C++ Implementation
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
// --- Problem 1: Kruskal's MST with Disjoint Set Union (DSU) ---
struct Edge {
int u, v, weight;
};
struct DSU {
vector<int> parent;
vector<int> rank;
DSU(int n) {
parent.resize(n);
iota(parent.begin(), parent.end(), 0);
rank.assign(n, 0);
}
int find(int i) {
if (parent[i] == i) return i;
return parent[i] = find(parent[i]); // Path compression
}
bool unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i == root_j) return false;
// Union by rank
if (rank[root_i] < rank[root_j]) {
parent[root_i] = root_j;
} else if (rank[root_i] > rank[root_j]) {
parent[root_j] = root_i;
} else {
parent[root_j] = root_i;
rank[root_i]++;
}
return true;
}
};
int kruskalMST(int n, vector<Edge>& edges) {
sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
return a.weight < b.weight;
});
DSU dsu(n);
int mstWeight = 0;
int edgesAdded = 0;
for (const auto& edge : edges) {
if (dsu.unite(edge.u, edge.v)) {
mstWeight += edge.weight;
edgesAdded++;
if (edgesAdded == n - 1) break;
}
}
return (edgesAdded == n - 1) ? mstWeight : -1; // -1 if disconnected
}
// --- Problem 2: Gas Station Circuit ---
int canCompleteCircuit(const vector<int>& gas, const vector<int>& cost) {
int total_gas = 0, total_cost = 0;
int tank = 0, start = 0;
for (size_t i = 0; i < gas.size(); i++) {
total_gas += gas[i];
total_cost += cost[i];
tank += gas[i] - cost[i];
if (tank < 0) {
start = i + 1;
tank = 0; // Reset
}
}
return (total_gas < total_cost) ? -1 : start;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
// Test Kruskal MST
int n = 5;
vector<Edge> edges = {
{0, 1, 1}, {1, 2, 2}, {0, 2, 3}, {1, 3, 3},
{3, 4, 4}, {2, 4, 5}, {2, 3, 6}
};
cout << "Kruskal MST Total Weight: " << kruskalMST(n, edges) << "\n"; // Output: 10
// Test Gas Station
vector<int> gas = {1, 2, 3, 4, 5};
vector<int> cost = {3, 4, 5, 1, 2};
cout << "Optimal Gas Station Start Index: " << canCompleteCircuit(gas, cost) << "\n"; // Output: 3
return 0;
}
◇References & Navigation
- Previous Topic: ◇[PS] Hermit Arcana
- Related Notes: ◇Greedy Algorithms Introduction, ◇Minimum Spanning Tree